package mbsl.commons.games;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import mbsl.commons.Commons;
import mbsl.extensions.MissingDependencyException;
import mbsl.util.DependencyChecker;

import org.htmlparser.Node;
import org.htmlparser.NodeFilter;
import org.htmlparser.Parser;
import org.htmlparser.tags.Div;
import org.htmlparser.tags.LinkTag;
import org.htmlparser.tags.Span;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;

public class MobyGamesMiner {
	private static Map<String, MobyDetails> mDetailsCache;
	private static Map<String, List<MobyHit>> mHitCache;
	
	private List<MobyHit> mHits;
	
	public MobyGamesMiner() throws MissingDependencyException {
		if (!DependencyChecker.check("org.htmlparser.Parser"))
			throw new MissingDependencyException(Commons.tp.get("games.moby.dependency"));
		
		if (mDetailsCache == null)
			mDetailsCache = new HashMap<String, MobyDetails>();
		if (mHitCache == null)
			mHitCache = new HashMap<String, List<MobyHit>>();
		
		mHits = new ArrayList<MobyHit>();
	}
	
	@SuppressWarnings("serial")
	List<MobyHit> search(String pSearchString) {
		if (pSearchString == null || pSearchString.length() < 3)
			return mHits;
		
		if (mHitCache.containsKey(pSearchString)) {
			mHits = mHitCache.get(pSearchString);
			
			return mHits;
		}

		try {
			// Get our parser
			Parser parser = new Parser(String.format(MOBY_SEARCH_URL, pSearchString));
			// Extract the table tags with SUMMARY="Game Match" (which, conveniently, hold the matches)
			NodeList hitList = parser.parse(null).extractAllNodesThatMatch(new NodeFilter() {
				public boolean accept(Node pNode) {
					if (!Div.class.isInstance(pNode))
						return false;
					
					Div node = (Div)pNode;
					
					if (node.getAttribute("class") == null)
						return false;
					if (node.getAttribute("class").equals("searchData"))
						return true;
					
					return false;
				}
			}, true);
			// For each of these, extract the relevant data
			for (int i = 0; i < hitList.size(); i++) {
				String name = hitList.elementAt(i).getChildren().elementAt(0).getChildren().elementAt(1).getChildren().elementAt(0).getText();
System.out.println(name);				
				Map<String, String> platforms = new HashMap<String, String>();
				
				NodeList platformNodes = hitList.elementAt(i).getChildren().elementAt(1).getChildren().extractAllNodesThatMatch(new NodeFilter() {
					public boolean accept(Node pNode) {
						if (!Span.class.isInstance(pNode))
							return false;
						
						Span node = (Span)pNode;

						if (node.getAttribute("style") == null)
							return false;
						if (node.getAttribute("style").equals("white-space: nowrap"))
							return true;
						else
							return false;
					}
				});
				System.out.println(platformNodes.size());
				for (int node = 0; node < platformNodes.size(); node++) {
					Span platformNode = (Span)platformNodes.elementAt(node);
					String platformName;
					String year;
					String platformLink;
					
					if (LinkTag.class.isInstance(platformNode.getChild(0))) {
						LinkTag link = (LinkTag)platformNode.getChild(0);
						platformName = link.getLinkText();
						platformLink = link.getLink();
						year = platformNode.getChild(3).getText();
					} else {
						platformName = platformNodes.elementAt(node).getChildren().elementAt(0).getText();
						year = platformNode.getChild(2).getText();
						platformLink = null;
					}
					
					if (platformName.endsWith(" ("))
						platformName = platformName.substring(0, platformName.length() - 2);
					
					platforms.put(platformName + "(" + year + ")", platformLink);
					
					System.out.println(platformName);
					System.out.println(year);
					System.out.println(platformLink);
				}
				System.out.println();
//				System.out.println(hitList.elementAt(i).getChildren().elementAt(0).getChildren().elementAt(1).getChildren().elementAt(0).getText());
//System.out.println();
//				System.out.println(name);				
/*				NodeList nodeList = hitList.elementAt(i).getChildren().elementAt(0).getChildren().extractAllNodesThatMatch(new NodeFilter() {
					public boolean accept(Node pNode) {
						if (!Div.class.isInstance(pNode))
							return false;
						
						Div node = (Div)pNode;
						
						if (node.getAttribute("class") == null)
							return false;
						if (node.getAttribute("class").equals("searchTitle"))
							return true;
						
						return false;
					}
				});
				// The location
				String location = ((LinkTag)nodeList.elementAt(0).getChildren().elementAt(0)).getAttribute("href");
				// We require it to be a full address.
				if (!location.startsWith("http://www.mobygames.com"))
					location = "http://www.mobygames.com" + location;
				
				String name = nodeList.elementAt(0).getChildren().elementAt(0).getChildren().elementAt(0).getText();
				String publisher = nodeList.elementAt(0).getChildren().elementAt(2).getText();
				String platform = nodeList.elementAt(0).getChildren().elementAt(4).getText();
				// Alter this if you're differently inclined.
				if (platform.equals("Windows") || platform.equals("DOS") || platform.equals("PC Booter"))
					mHits.add(new MobyHit(name, platform, publisher, location));
*/			}
			
			mHitCache.put(pSearchString, mHits);
			
			return mHits;
		} catch (ParserException e) {
			return null;
		}
	}
	MobyDetails get(int pIndex) {
		MobyDetails details = get(mHits.get(pIndex).getLocation());
		
		if (details == null) // Take two
			details = get(mHits.get(pIndex).getLocation());
		
		return details;
	}
	@SuppressWarnings("serial")
	synchronized MobyDetails get(String pLocation) {
		MobyDetails details = mDetailsCache.get(pLocation);
		
		if (details != null)
			return mDetailsCache.get(pLocation);

		try {
			// Declare our vars.
			String title = null;
			String publisher = null;
			String developer = null;
			String releaseDate = null;
			String platforms = null;
			String genre = null;
			String perspective = null;
			String nonSport = null;
			String misc = null;
			String mobyRank = null;
			String mobyScore = null;
			NodeList tempList;
			// This filter simply filters out everything that's not a <div />
			NodeFilter divFilter = new NodeFilter() {
				public boolean accept(Node pNode) {
					if (Div.class.isInstance(pNode))
						return true;
					else
						return false;
				}
			};
			// Create a new parser.
			Parser parser = new Parser(pLocation);
			// Parse, and filter out everything we're not interested in.
			// Inevitably, we'll end up with a list of four nodes: 
			// 1. Title data 
			// 2. Release data
			// 3. Genre data
			// 4. Rank info
			NodeList nodeList = parser.parse(new NodeFilter() {
				@Override
				public boolean accept(Node pNode) {
					if (!Div.class.isInstance(pNode))
						return false;

					String idTag = ((Div)pNode).getAttribute("id");
					
					if (idTag == null)
						return false;
					if (idTag.equals("gameTitle"))
						return true;
					if (idTag.equals("coreGameRelease"))
						return true;
					if (idTag.equals("coreGameGenre"))
						return true;
					if (idTag.equals("coreGameRank"))
						return true;

					return false;
				}
			});
			// Extract the title.
			title = nodeList.elementAt(0).getChildren().elementAt(0).getChildren().elementAt(0).getText();
			// Set the list to release data.
			tempList = nodeList.elementAt(1).getChildren().extractAllNodesThatMatch(divFilter);
			// For each subnode, find out what it's holding, and extract it.
			for (int i = 0; i < tempList.size(); i += 2) {
				String description = tempList.elementAt(i).getChildren().elementAt(0).getText();

				if (description.equals("Published by"))
					publisher = tempList.elementAt(i + 1).getChildren().elementAt(0).getChildren().elementAt(0).getText();
				else if (description.equals("Developed by"))
					developer = tempList.elementAt(i + 1).getChildren().elementAt(0).getChildren().elementAt(0).getText();
				else if (description.equals("Released"))
					releaseDate = tempList.elementAt(i + 1).getChildren().elementAt(0).getChildren().elementAt(0).getText();
				else if (description.equals("Also For")) {
					StringBuilder stringBuilder = new StringBuilder();
					
					for (int j = 0; j < tempList.elementAt(i + 1).getChildren().size() - 1; j += 2) {
						Node node = tempList.elementAt(i + 1).getChildren().elementAt(j);

						if (j != 0)
							stringBuilder.append(", ");

						stringBuilder.append(node.getChildren().elementAt(0).getText());
					}
					
					platforms = stringBuilder.toString();
				}
			}
			// Set the list to genre data.
			tempList = nodeList.elementAt(2).getChildren().extractAllNodesThatMatch(divFilter);
			
			for (int i = 0; i < tempList.size(); i += 2) {
				String description = tempList.elementAt(i).getChildren().elementAt(0).getText();
				
				if (description.equals("Genre"))
					genre = extractList(tempList.elementAt(i + 1));
				else if (description.equals("Perspective"))
					perspective = extractList(tempList.elementAt(i + 1));
				else if (description.equals("Non-Sport"))
					nonSport = extractList(tempList.elementAt(i + 1));
				else if (description.equals("Misc"))
					misc = extractList(tempList.elementAt(i + 1));
			}
			// Set the list to rank info
			tempList = nodeList.elementAt(3).getChildren().extractAllNodesThatMatch(new NodeFilter() {
				public boolean accept(Node pNode) {
					if (!Div.class.isInstance(pNode))
						return false;
					
					Div node = (Div)pNode;
					
					if (node.getAttribute("class") != null && node.getAttribute("class").startsWith("fr score"))
						return true;
					
					return false;
				}
			}, true);
			
			mobyRank = tempList.elementAt(0).getChildren().elementAt(0).getText();
			mobyScore = tempList.elementAt(1).getChildren().elementAt(0).getText();
			// Save the details.
			details = new MobyDetails(title, publisher, developer, releaseDate, platforms, genre, perspective, nonSport, misc, mobyRank, mobyScore);
			
			mDetailsCache.put(pLocation, details);
			
			return details;
		} catch (ParserException e) {
			return null;
		}
	}
	
	private String extractList(Node pNode) {
		StringBuilder stringBuilder = new StringBuilder();
		
		for (int j = 0; j < pNode.getChildren().size(); j += 2) {
			Node node = pNode.getChildren().elementAt(j);

			if (j != 0)
				stringBuilder.append(", ");

			stringBuilder.append(node.getChildren().elementAt(0).getText());
		}
		
		return stringBuilder.toString();
	}
	
	public static void main(String[] pArgs) throws Exception {
		final MobyGamesMiner m = new MobyGamesMiner();
		
		List<MobyHit> l = m.search("Call of Duty world at war");
		
		for (MobyHit h : l)
			System.out.println(h);
	}
	
	private static final String MOBY_SEARCH_URL = "http://www.mobygames.com/search/quick?q=%1$s&search=Go&sFilter=1&sG=on";

	class MobyHit {
		private MobyHit(String pName, String pPlatform, String pPublisher, String pLocations) {
			mName = pName;
			mLocation = pLocations;
			mPlatform = pPlatform;
			mPublisher = pPublisher;
		}
		
		String getName() {
			return decodeHTML(mName);
		}
		String getPlatform() {
			return decodeHTML(mPlatform);
		}
		String getPublisher() {
			return decodeHTML(mPublisher);
		}
		String getLocation() {
			return mLocation;
		}
		public String toString() {
			return String.format("%s (%s, %s)", getName(), getPlatform(), getPublisher());
		}

		private String mName;
		private String mPlatform;
		private String mPublisher;
		private String mLocation;
	}
	class MobyDetails {
		private String mName;
		private String mPublisher;
		private String mDeveloper;
		private String mReleaseDate;
		private String mPlatforms;
		private String mGenre;
		private String mPerspective;
		private String mNonSport;
		private String mMisc;
		private String mMobyRank;
		private String mMobyScore;
		
		private MobyDetails(String pName, String pPublisher, String pDeveloper, String pReleaseDate, String pPlatforms, String pGenre, String pPerspective, String pNonSport, String pMisc, String pMobyRank, String pMobyScore) {
			mName = pName;
			mPublisher = pPublisher;
			mDeveloper = pDeveloper;
			mReleaseDate = pReleaseDate;
			mPlatforms = pPlatforms;
			mGenre = pGenre;
			mPerspective = pPerspective;
			mNonSport = pNonSport;
			mMisc = pMisc;
			mMobyRank = pMobyRank;
			mMobyScore = pMobyScore;
		}
		String getName() {
			return decodeHTML(mName);
		}
		String getPublisher() {
			return decodeHTML(mPublisher);
		}

		String getDeveloper() {
			return decodeHTML(mDeveloper);
		}
		/** Returns the year as a size 4 String, or null if the year couldn't be retrieved. */
		String getYear() {
			String year = mReleaseDate.substring(mReleaseDate.length() - 4);

			if (year.matches("[12][901][0-9][0-9]"))
				return mReleaseDate.substring(mReleaseDate.length() - 4);
			else 
				return null;
		}

		String getPlatforms() {
			return decodeHTML(mPlatforms);
		}

		String getGenre() {
			return decodeHTML(mGenre);
		}

		String getPerspective() {
			return decodeHTML(mPerspective);
		}

		String getNonSport() {
			return decodeHTML(mNonSport);
		}

		String getMisc() {
			return decodeHTML(mMisc);
		}

		int getMobyRank() {
			if (mMobyRank.equals("..."))
				return -1;
			else
				return Integer.parseInt(mMobyRank);
		}

		double getMobyScore() {
			if (mMobyScore.equals("..."))
				return -1;
			else
				return Double.parseDouble(mMobyScore);
		}
		
		public String toString() {
			return String.format("Name: %s\nPublisher: %s\nDeveloper: %s\nRelease Date: %s\nPlatforms: %s\nGenre: %s\nPerspective: %s\nNon-Sport: %s\nMisc: %s\nMobyRank: %s\nMobyScore: %s",
					getName(),
					getPublisher(),
					getDeveloper(),
					getYear(),
					getPlatforms(),
					getGenre(),
					getPerspective(),
					getNonSport(),
					getMisc(),
					getMobyRank(),
					getMobyScore()
			);
		}
	}
	
	private String decodeHTML(String pInput) {
		return pInput
		.replaceAll("&nbsp;", " ")
		.replaceAll("&#x26;", "&")
		.replaceAll("&#x27;", "'")
		;
	}
}
